﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Dynamic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq.Expressions;
using MvcBoost.EntityFramework.Validation;

namespace System.Data.Entity
{
    public class DbContextGenericResourceService<TContext,TResource> : DbContextService<TContext>
        where TContext : DbContext
        where TResource : class
    {

        public DbContextGenericResourceService(TContext context)
            : base(context)
        {
        }

        public virtual List<TDerived> List<TDerived>() where TDerived : TResource
        {
            return Context.Set<TResource>().OfType<TDerived>().ToList();
        }

        public virtual List<TResource> List()
        {
            return Context.Set<TResource>().ToList();
        }

        public virtual TDerived Create<TDerived>() where TDerived : TResource, new()
        {
            return Create<TDerived>(null);
        }

        public virtual TDerived Create<TDerived>(Action<TDerived> initAction) where TDerived : TResource, new()
        {
            // TODO: Check for DbSet().Create<TDerived>()
            TDerived obj = new TDerived();            
            if (initAction != null) initAction(obj);
            return obj;
        }

        //public virtual TDerived Create<TDerived>(Action<TDerived> initAction, object context) where TDerived : TResource, new()
        //{
        //    dynamic ctxObject = MvcTools.Extend<ExpandoObject>(context);

        //    TDerived obj = new TDerived();
        //    if (context!=null) Initialize(obj, context);
        //    if (initAction != null) initAction(obj);
        //    return obj;
        //}        

        //protected virtual void Initialize(TResource obj, dynamic initContext)
        //{
        //    // Do nothing
        //}

        
        //private static IList<IValueSetter<TContext,TResource>> _defaultValues;

        //protected IList<IValueSetter<TContext, TResource>> DefaultValues
        //{
        //    get
        //    {
        //        if (_defaultValues == null)
        //        {
        //            _defaultValues = new List<IValueSetter<TContext, TResource>>(GetDefaultValues());                    
        //        }
        //        return _defaultValues;
        //    }
        //}

        //public virtual IEnumerable<IValueSetter<TContext, TResource>> GetDefaultValues()
        //{
        //    yield break;
        //}

        private static IList<IDbContextResourceValidator<TContext, TResource>> _validators;
        protected IList<IDbContextResourceValidator<TContext, TResource>> Validators
        {
            get
            {
                if (_validators == null)
                {
                    // http://stackoverflow.com/questions/2432821/iterator-blocks-and-inheritance
                    _validators = new List<IDbContextResourceValidator<TContext, TResource>>(GetValidators());
                }
                return _validators;
            }
        }

        protected virtual IEnumerable<IDbContextResourceValidator<TContext, TResource>> GetValidators()
        {
            // Do nothing
            // TODO: Comprobar que esto se puede heredar
            yield break;
        }
        

        public virtual TResource GetObjectByID(params object[] keyValues)
        {
            TResource obj = Context.Set<TResource>().Find(keyValues);
            if (obj == null) throw new KeyNotFoundException(String.Format("Could not find the requested object of type '{0}' by id: [{1}] ", typeof(TResource).FullName,string.Join(",", keyValues)));
            return obj;
        }

        public virtual bool TryGetObjectByID(out TResource resource, params object[] keyValues)
        {
            resource = Context.Set<TResource>().Find(keyValues);
            return (resource != null);
        }
                
        /// <summary>
        /// Devuelve un objeto hijo de otro superior, asegurándose de que la relación existe (es decir, que el objeto es hijo de quien lo solicita)
        /// </summary>
        /// <typeparam name="TChild">Tipo del objeto hijo</typeparam>
        /// <param name="collection">Colección donde comparar</param>
        /// <param name="keyExpression">Expresión que se utilizará en la comparación(para verificar si existe o no el elemento)</param>
        /// <param name="keyValues">Claves primarias del objeto</param>
        /// <returns></returns>
        public virtual TChild GetChildObjectByID<TChild,TKey>( IEnumerable<TChild> collection, Expression<Func<TChild,TKey>> keyExpression, params object[] keyValues) where TChild: class
        {
            if (collection == null) throw new ArgumentNullException("collection");
            if (keyExpression == null) throw new ArgumentNullException("keyExpression");

            var k = keyExpression.Compile();

            var child = Context.Set<TChild>().Find(keyValues);
            if (child == null) throw new KeyNotFoundException(String.Format("Could not find the requested object of type '{0}' by id: [{1}] ", typeof(TChild).FullName, string.Join(",", keyValues)));

            if (!collection.Exists(x => k(child).Equals(k(x))))
                throw new KeyNotFoundException(String.Format("Requested object of type '{0}' with keys [{1}] is not CHILD of the invoker", typeof(TChild).FullName, string.Join(",", keyValues)));

            return child;
        }

        public virtual TRelated GetRelatedObjectByID<TRelated>(Expression<Func<TResource, TRelated>> expression, bool required, params object[] keyValues) where TRelated : class 
        {
            TRelated related = Context.Set<TRelated>().Find(keyValues);
            if ((required) && (related == null)) throw new KeyNotFoundException(String.Format("Could not find the requested object of type '{0}' by keys: [{1}]", typeof(TRelated).FullName, string.Join(",", keyValues)));
            return related;
        }

        //private void SetDefaultValues(TResource resource, bool isCreating)
        //{
        //    foreach (var valueSetter in DefaultValues)
        //    {
        //        if (valueSetter.Matches(Context, resource, isCreating)) valueSetter.SetValue(Context, resource);
        //    }
        //}

        public virtual void Add(TResource resource)
        {
            if (resource == null) throw new ArgumentNullException("resource");

            //SetDefaultValues(resource, true);

            if (!IsValid(resource,true)) throw new InvalidOperationException("Resource is not valid");
            Context.Set<TResource>().Add(resource);            
        }

        public virtual void Add(IEnumerable<TResource> resources)
        {
            if (resources == null) throw new ArgumentNullException("resources");
            foreach (var r in resources)
            {
                Add(r);
            }
        }
        

        public virtual void Update(TResource resource)
        {
            if (!IsValid(resource,false)) throw new InvalidOperationException("Resource cannot be updated");
            MarkAsModified<TResource>(resource);
        }

        public virtual void Delete(TResource resource)
        {
            if (resource == null) throw new ArgumentNullException("resource");
            Context.Set<TResource>().Remove(resource);
        }

        protected virtual void AddChild<TChild>(IList<TChild> childCol, TChild item) where TChild : class
        {
            if (childCol == null) throw new ArgumentNullException("childCol");
            AddChild<TChild>(x => childCol.Add(x), item);
        }

        protected virtual void AddChild<TChild>(Action<TChild> addChildMethod, TChild item) where TChild : class
        {
            if (addChildMethod == null) throw new ArgumentNullException("addChildMethod");
            addChildMethod(item);
            Context.Set<TChild>().Add(item);
        }

        protected virtual void RemoveChild<TChild>(IList<TChild> childCol, TChild item) where TChild : class
        {
            childCol.Remove(item);
            Context.Set<TChild>().Remove(item);            
        }

        public virtual bool IsValid(TResource resource, bool isCreating)
        {
            foreach (var validator in Validators)
            {
                if (validator.Matches(Context,resource,isCreating)) 
                {
                if (!validator.IsValid(Context, resource, isCreating)) return false;
                }
            }
            return true;
        }

        

        protected virtual CheckWhenCreatingValidator<TContext, TResource> CheckWhenCreating(Func<TContext, TResource, bool> validationCheck)
        {
            return new CheckWhenCreatingValidator<TContext, TResource>(validationCheck);
        }

        protected virtual CheckWhenEditingValidator<TContext, TResource> CheckWhenEditing(Func<TContext, TResource, bool> validationCheck)
        {
            return new CheckWhenEditingValidator<TContext, TResource>(validationCheck);
        }

        protected virtual CheckWhenCreatingAndEditingValidator<TContext, TResource> CheckWhenCreatingAndEditing(Func<TContext, TResource, bool> validationCheck)
        {
            return new CheckWhenCreatingAndEditingValidator<TContext, TResource>(validationCheck);
        }

        //protected virtual IValueSetter<TContext, TResource> SetDefaultValueWhenCreating(Func<TContext, TResource, bool> condition, Action<TContext, TResource> action)
        //{
        //    return new GenericValueSetterBase<TContext, TResource>(condition, action);
        //}

        //protected virtual IValueSetter<TContext, TResource> SetDefaultValueWhenCreating(Action<TContext, TResource> action)
        //{
        //    return new GenericValueSetterBase<TContext, TResource>(action);
        //}


        //public virtual DefaultFlyweightResourceFactory<TSource, TResource> FlyweightFactoryForCreation<TSource>()
        //{
        //    return new DefaultFlyweightResourceFactory<TSource, TResource>();
        //}


       
        
    }
}
